var FMFPentityToPolyline = Class.create();
FMFPentityToPolyline.prototype = {
	initialize: function () {
	},

	/**
 	* Rotate a set of points.
 	*
 	* @param points the points
 	* @param angle the rotation angle
 	*/
	rotate: function (points, angle) {
		return points.map(function (p) {
			return [
				p[0] * Math.cos(angle) - p[1] * Math.sin(angle),
				p[1] * Math.cos(angle) + p[0] * Math.sin(angle)
			];
		});
	},

    /**
 	* Interpolate an ellipse
 	* @param cx center X
 	* @param cy center Y
 	* @param rx radius X
 	* @param ry radius Y
 	* @param start start angle in radians
 	* @param start end angle in radians
 	*/
	interpolateEllipse: function (cx, cy, rx, ry, start, end, rotationAngle) {
		if (end < start) {
			end += Math.PI * 2;
		}

		// ----- Relative points -----

		// Start point
		var points = [];
		var dTheta = Math.PI * 2 / 72;
		var EPS = 1e-6;
		for (var theta = start; theta < end - EPS; theta += dTheta) {
			points.push([
				Math.cos(theta) * rx,
				Math.sin(theta) * ry
			]);
		}
		points.push([
			Math.cos(end) * rx,
			Math.sin(end) * ry
		]);

		// ----- Rotate -----
		if (rotationAngle) {
			points = this.rotate(points, rotationAngle);
		}

		// ----- Offset center -----
		points = points.map(function (p) {
			return [cx + p[0], cy + p[1]];
		});

		return points;
	},

    /**
 	* Interpolate a b-spline. The algorithm examins the knot vector
 	* to create segments for interpolation. The parameterisation value
 	* is re-normalised back to [0,1] as that is what the lib expects (
 	* and t i de-normalised in the b-spline library)
 	*
	* @param controlPoints the control points
 	* @param degree the b-spline degree
	* @param knots the knot vector
 	* @returns the polyline
 	*/
	interpolateBSpline: function (controlPoints, degree, knots, interpolationsPerSplineSegment) {
		var polyline = [];
		var controlPointsForLib = controlPoints.map(function (p) {
			return [p.x, p.y];
		});
		var segmentTs = [knots[degree]];
		var domain = [knots[degree], knots[knots.length - 1 - degree]];

		for (var k = degree + 1; k < knots.length - degree; ++k) {
			if (segmentTs[segmentTs.length - 1] !== knots[k]) {
				segmentTs.push(knots[k]);
			}
		}

		interpolationsPerSplineSegment = interpolationsPerSplineSegment || 25;

		for (var i = 1; i < segmentTs.length; ++i) {
			var uMin = segmentTs[i - 1];
			var uMax = segmentTs[i];

			for (var k = 0; k <= interpolationsPerSplineSegment; ++k) {
				var u = k / interpolationsPerSplineSegment * (uMax - uMin) + uMin;
				var t = (u - domain[0]) / (domain[1] - domain[0]);
				var p = new FMFPbSpline().bSpline(t, degree, controlPointsForLib, knots);
				polyline.push(p);
			}
		}
		return polyline;
	},

    /**
    * Convert a parsed DXF entity to a polyline. These can be used to render the
    * the DXF in SVG, Canvas, WebGL etc., without depending on native support
    * of primitive objects (ellispe, spline etc.)
    */
	toPolyline: function (entity, options) {
		var objectContext = this;
		options = options || {};
		var polyline;

		if (entity.type === 'LINE') {
			polyline = [[entity.vertices[0].x, entity.vertices[0].y], [entity.vertices[1].x, entity.vertices[1].y]];
		}

		if (entity.type === 'LWPOLYLINE' || entity.type === 'POLYLINE') {
			polyline = [];

			if (entity.polygonMesh || entity.polyfaceMesh) {
				// Do not attempt to render meshes
			} else if (entity.vertices.length) {
				if (entity.shape) {
					entity.vertices = entity.vertices.concat(entity.vertices[0]);
				}

				for (var i = 0; i < entity.vertices.length - 1; ++i) {
					var from = [entity.vertices[i].x, entity.vertices[i].y];
					var to = [entity.vertices[i + 1].x, entity.vertices[i + 1].y];
					polyline.push(from);

					if (entity.vertices[i].bulge) {
						polyline = polyline.concat(
							objectContext.createArcForLWPolyine(from, to, entity.vertices[i].bulge));
					}

					// The last iteration of the for loop
					if (i === entity.vertices.length - 2) {
						polyline.push(to);
					}
				}
			} else {
				//logger.warn('Polyline entity with no vertices');
			}
		}

		if (entity.type === 'CIRCLE') {
			polyline = objectContext.interpolateEllipse(
				entity.center.x, entity.center.y,
				entity.radius, entity.radius,
				0, Math.PI * 2
			);

			if (entity.extrusionZ === -1) {
				polyline = polyline.map(function (p) {
					return [-p[0], p[1]];
				});
			}
		}

		if (entity.type === 'ELLIPSE') {
			var rx = Math.sqrt(entity.majorX * entity.majorX + entity.majorY * entity.majorY);
			var ry = entity.axisRatio * rx;
			var majorAxisRotation = -Math.atan2(-entity.majorY, entity.majorX);
			polyline = objectContext.interpolateEllipse(
				entity.x, entity.y,
				rx, ry,
				entity.startAngle, entity.endAngle,
				majorAxisRotation
			);

			if (entity.extrusionDirection.z === -1) {
				polyline = polyline.map(function (p) {
					return [-p[0], p[1]];
				});
			}
		}

		if (entity.type === 'ARC') {
			// Why on earth DXF has degree start & end angles for arc,
			// and radian start & end angles for ellipses is a mystery
			polyline = objectContext.interpolateEllipse(
				entity.center.x, entity.center.y,
				entity.radius, entity.radius,
				entity.startAngle, entity.endAngle,
				undefined, false
			);

			// I kid you not, ARCs and ELLIPSEs handle this differently,
			// as evidenced by how AutoCAD actually renders these entities
			if (entity.extrusionDirection.z === -1) {
				polyline = polyline.map(function (p) {
					return [-p[0], p[1]];
				});
			}
		}

		if (entity.type === 'SPLINE') {
			if (options.interpolationsPerSplineSegment)
				polyline = objectContext.interpolateBSpline(
					entity.controlPoints, entity.degreeOfSplineCurve,
					entity.knotValues,
					options.interpolationsPerSplineSegment
				);
			else
				polyline = objectContext.interpolateBSpline(
					entity.controlPoints, entity.degreeOfSplineCurve,
					entity.knotValues, null
				);
		}

		if (!polyline) {
			//logger.warn('unsupported entity for converting to polyline:', entity.type);
			return [];
		}

		return polyline;
	},

    /**
    * Create the arcs point for a LWPOLYLINE. The start and end are excluded
    *
    */
	createArcForLWPolyine: function (from, to, bulge, resolution) {

		// Resolution in degrees
		if (!resolution) {
			resolution = 5;
		}

		// If the bulge is < 0, the arc goes clockwise. So we simply
		// reverse a and b and invert sign
		// Bulge = tan(theta/4)
		var theta;
		var a;
		var b;

		if (bulge < 0) {
			theta = Math.atan(-bulge) * 4;
			a = new FMFPconverterViewBoxHelper(from[0], from[1]);
			b = new FMFPconverterViewBoxHelper(to[0], to[1]);
		} else {
			// Default is counter-clockwise
			theta = Math.atan(bulge) * 4;
			a = new FMFPconverterViewBoxHelper(to[0], to[1]);
			b = new FMFPconverterViewBoxHelper(from[0], from[1]);
		}

		var ab = b.sub(a);
		var lengthAB = ab.length();
		var c = a.add(ab.multiply(0.5));

		// Distance from center of arc to line between form and to points
		var lengthCD = Math.abs((lengthAB / 2) / Math.tan(theta / 2));
		var normAB = ab.norm();
		var d;

		if (theta < Math.PI) {
			var normDC = new FMFPconverterViewBoxHelper(
				normAB.x * Math.cos(Math.PI / 2) - normAB.y * Math.sin(Math.PI / 2),
				normAB.y * Math.cos(Math.PI / 2) + normAB.x * Math.sin(Math.PI / 2)
			);

			// D is the center of the arc
			d = c.add(normDC.multiply(-lengthCD));

		} else {
			var normCD = new FMFPconverterViewBoxHelper(
				normAB.x * Math.cos(Math.PI / 2) - normAB.y * Math.sin(Math.PI / 2),
				normAB.y * Math.cos(Math.PI / 2) + normAB.x * Math.sin(Math.PI / 2)
			);

			// D is the center of the arc
			d = c.add(normCD.multiply(lengthCD));
		}

		// Add points between start start and eng angle relative
		// to the center point
		var startAngle = Math.atan2(b.y - d.y, b.x - d.x) / Math.PI * 180;
		var endAngle = Math.atan2(a.y - d.y, a.x - d.x) / Math.PI * 180;

		if (endAngle < startAngle) {
			endAngle += 360;
		}

		var r = b.sub(d).length();
		var startInter = Math.floor(startAngle / resolution) * resolution + resolution;
		var endInter = Math.ceil(endAngle / resolution) * resolution - resolution;
		var points = [];

		for (var i = startInter; i <= endInter; i += resolution) {
			points.push(d.add(new FMFPconverterViewBoxHelper(
				Math.cos(i / 180 * Math.PI) * r,
				Math.sin(i / 180 * Math.PI) * r
			)));
		}

		// Maintain the right ordering to join the from and to points
		if (bulge < 0) {
			points.reverse();
		}

		return points.map(function (p) {
			return [p.x, p.y];
		});
	},

	type: 'FMFPentityToPolyline'
};